/*
 * Microsoft JDBC Driver for SQL Server
 * 
 * Copyright(c) Microsoft Corporation All rights reserved.
 * 
 * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information.
 */

package com.microsoft.sqlserver.jdbc;

import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.sql.SQLPermission;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.text.MessageFormat;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;

/**
 * SQLServerConnectionPoolProxy is a wrapper around SQLServerConnection object. When returning a connection object from PooledConnection.getConnection
 * we return this proxy per SPEC.
 * <p>
 * This class's public functions need to be kept identical to the SQLServerConnection's.
 * <p>
 * The API javadoc for JDBC API methods that this class implements are not repeated here. Please see Sun's JDBC API interfaces javadoc for those
 * details.
 */

class SQLServerConnectionPoolProxy implements ISQLServerConnection {
    private SQLServerConnection wrappedConnection;
    private boolean bIsOpen;
    static private final AtomicInteger baseConnectionID = new AtomicInteger(0);       // connection id dispenser
    final private String traceID;

    // Permission targets
    // currently only callAbort is implemented
    private static final String callAbortPerm = "callAbort";

    /**
     * Generate the next unique connection id.
     * 
     * @return the next conn id
     */
    /* L0 */ private static int nextConnectionID() {
        return baseConnectionID.incrementAndGet();
    }

    public String toString() {
        return traceID;
    }

    /* L0 */ SQLServerConnectionPoolProxy(SQLServerConnection con) {
        traceID = " ProxyConnectionID:" + nextConnectionID();
        wrappedConnection = con;
        // the Proxy is created with an open conn
        con.setAssociatedProxy(this);
        bIsOpen = true;
    }

    /* L0 */ void checkClosed() throws SQLServerException {
        if (!bIsOpen) {
            SQLServerException.makeFromDriverError(null, null, SQLServerException.getErrString("R_connectionIsClosed"), null, false);
        }
    }

    /* L0 */ public Statement createStatement() throws SQLServerException {
        checkClosed();
        return wrappedConnection.createStatement();
    }

    /* L0 */ public PreparedStatement prepareStatement(String sql) throws SQLServerException {
        checkClosed();
        return wrappedConnection.prepareStatement(sql);
    }

    /* L0 */ public CallableStatement prepareCall(String sql) throws SQLServerException {
        checkClosed();
        return wrappedConnection.prepareCall(sql);
    }

    /* L0 */ public String nativeSQL(String sql) throws SQLServerException {
        checkClosed();
        return wrappedConnection.nativeSQL(sql);
    }

    public void setAutoCommit(boolean newAutoCommitMode) throws SQLServerException {
        checkClosed();
        wrappedConnection.setAutoCommit(newAutoCommitMode);
    }

    /* L0 */ public boolean getAutoCommit() throws SQLServerException {
        checkClosed();
        return wrappedConnection.getAutoCommit();
    }

    public void commit() throws SQLServerException {
        checkClosed();
        wrappedConnection.commit();
    }

    /**
     * Rollback a transaction.
     *
     * @throws SQLServerException
     *             if no transaction exists or if the connection is in auto-commit mode.
     */
    public void rollback() throws SQLServerException {
        checkClosed();
        wrappedConnection.rollback();
    }

    public void abort(Executor executor) throws SQLException {
        if (!bIsOpen || (null == wrappedConnection))
            return;

        if (null == executor) {
            MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_invalidArgument"));
            Object[] msgArgs = {"executor"};
            SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, false);
        }

        // check for callAbort permission
        SecurityManager secMgr = System.getSecurityManager();
        if (secMgr != null) {
            try {
                SQLPermission perm = new SQLPermission(callAbortPerm);
                secMgr.checkPermission(perm);
            }
            catch (SecurityException ex) {
                MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_permissionDenied"));
                Object[] msgArgs = {callAbortPerm};
                throw new SQLServerException(form.format(msgArgs), null, 0, ex);
            }
        }

        bIsOpen = false;

        executor.execute(new Runnable() {
            public void run() {
                if (wrappedConnection.getConnectionLogger().isLoggable(Level.FINER))
                    wrappedConnection.getConnectionLogger().finer(toString() + " Connection proxy aborted ");
                try {
                    wrappedConnection.poolCloseEventNotify();
                    wrappedConnection = null;
                }
                catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
        });
    }

    /* L0 */ public void close() throws SQLServerException {
        if (bIsOpen && (null != wrappedConnection)) {
            if (wrappedConnection.getConnectionLogger().isLoggable(Level.FINER))
                wrappedConnection.getConnectionLogger().finer(toString() + " Connection proxy closed ");
            wrappedConnection.poolCloseEventNotify();
            wrappedConnection = null;
        }
        bIsOpen = false;
    }

    /* L0 */ void internalClose() {
        bIsOpen = false;
        wrappedConnection = null;
    }

    /* L0 */ public boolean isClosed() throws SQLServerException {
        return !bIsOpen;
    }

    /* L0 */ public DatabaseMetaData getMetaData() throws SQLServerException {
        checkClosed();
        return wrappedConnection.getMetaData();
    }

    /* L0 */ public void setReadOnly(boolean readOnly) throws SQLServerException {
        checkClosed();
        wrappedConnection.setReadOnly(readOnly);
    }

    /* L0 */ public boolean isReadOnly() throws SQLServerException {
        checkClosed();
        return wrappedConnection.isReadOnly();
    }

    /* L0 */ public void setCatalog(String catalog) throws SQLServerException {
        checkClosed();
        wrappedConnection.setCatalog(catalog);
    }

    /* L0 */ public String getCatalog() throws SQLServerException {
        checkClosed();
        return wrappedConnection.getCatalog();
    }

    /* L0 */ public void setTransactionIsolation(int level) throws SQLServerException {
        checkClosed();
        wrappedConnection.setTransactionIsolation(level);
    }

    /* L0 */ public int getTransactionIsolation() throws SQLServerException {
        checkClosed();
        return wrappedConnection.getTransactionIsolation();
    }

    /* L0 */ public SQLWarning getWarnings() throws SQLServerException {
        checkClosed();
        return wrappedConnection.getWarnings(); // Warnings support added
    }

    /* L2 */ public void clearWarnings() throws SQLServerException {
        checkClosed();
        wrappedConnection.clearWarnings();
    }

    // --------------------------JDBC 2.0-----------------------------

    /* L2 */ public Statement createStatement(int resultSetType,
            int resultSetConcurrency) throws SQLException {
        checkClosed();
        return wrappedConnection.createStatement(resultSetType, resultSetConcurrency);
    }

    /* L2 */ public PreparedStatement prepareStatement(String sSql,
            int resultSetType,
            int resultSetConcurrency) throws SQLException {
        checkClosed();
        return wrappedConnection.prepareStatement(sSql, resultSetType, resultSetConcurrency);
    }

    /* L2 */ public CallableStatement prepareCall(String sql,
            int resultSetType,
            int resultSetConcurrency) throws SQLException {
        checkClosed();
        return wrappedConnection.prepareCall(sql, resultSetType, resultSetConcurrency);
    }

    /* L2 */ public void setTypeMap(java.util.Map<String, Class<?>> map) throws SQLServerException {
        checkClosed();
        wrappedConnection.setTypeMap(map);
    }

    public java.util.Map<String, Class<?>> getTypeMap() throws SQLServerException {
        checkClosed();
        return wrappedConnection.getTypeMap();
    }

    /* L3 */ public Statement createStatement(int nType,
            int nConcur,
            int nHold) throws SQLServerException {
        checkClosed();
        return wrappedConnection.createStatement(nType, nConcur, nHold);
    }

    /**
     * Creates a <code>Statement</code> object that will generate <code>ResultSet</code> objects with the given type, concurrency, and holdability.
     * This method is the same as the <code>createStatement</code> method above, but it allows the default result set type, concurrency, and
     * holdability to be overridden.
     *
     * @param nType
     *            one of the following <code>ResultSet</code> constants: <code>ResultSet.TYPE_FORWARD_ONLY</code>,
     *            <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>
     * @param nConcur
     *            one of the following <code>ResultSet</code> constants: <code>ResultSet.CONCUR_READ_ONLY</code> or
     *            <code>ResultSet.CONCUR_UPDATABLE</code>
     * @param nHold
     *            one of the following <code>ResultSet</code> constants: <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> or
     *            <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>
     * @param stmtColEncSetting
     *            Specifies how data will be sent and received when reading and writing encrypted columns.
     * @return a new <code>Statement</code> object that will generate <code>ResultSet</code> objects with the given type, concurrency, and holdability
     * @exception SQLException
     *                if a database access error occurs, this method is called on a closed connection or the given parameters are not
     *                <code>ResultSet</code> constants indicating type, concurrency, and holdability
     * @exception SQLFeatureNotSupportedException
     *                if the JDBC driver does not support this method or this method is not supported for the specified result set type, result set
     *                holdability and result set concurrency.
     */
    public Statement createStatement(int nType,
            int nConcur,
            int nHold,
            SQLServerStatementColumnEncryptionSetting stmtColEncSetting) throws SQLServerException {
        checkClosed();
        return wrappedConnection.createStatement(nType, nConcur, nHold, stmtColEncSetting);
    }

    /* L3 */ public PreparedStatement prepareStatement(java.lang.String sql,
            int nType,
            int nConcur,
            int nHold) throws SQLServerException {
        checkClosed();
        return wrappedConnection.prepareStatement(sql, nType, nConcur, nHold);
    }

    /**
     * Creates a <code>PreparedStatement</code> object that will generate <code>ResultSet</code> objects with the given type, concurrency, and
     * holdability.
     * <P>
     * This method is the same as the <code>prepareStatement</code> method above, but it allows the default result set type, concurrency, and
     * holdability to be overridden.
     *
     * @param sql
     *            a <code>String</code> object that is the SQL statement to be sent to the database; may contain one or more '?' IN parameters
     * @param nType
     *            one of the following <code>ResultSet</code> constants: <code>ResultSet.TYPE_FORWARD_ONLY</code>,
     *            <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>
     * @param nConcur
     *            one of the following <code>ResultSet</code> constants: <code>ResultSet.CONCUR_READ_ONLY</code> or
     *            <code>ResultSet.CONCUR_UPDATABLE</code>
     * @param nHold
     *            one of the following <code>ResultSet</code> constants: <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> or
     *            <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>
     * @param stmtColEncSetting
     *            Specifies how data will be sent and received when reading and writing encrypted columns.
     * @return a new <code>PreparedStatement</code> object, containing the pre-compiled SQL statement, that will generate <code>ResultSet</code>
     *         objects with the given type, concurrency, and holdability
     * @exception SQLException
     *                if a database access error occurs, this method is called on a closed connection or the given parameters are not
     *                <code>ResultSet</code> constants indicating type, concurrency, and holdability
     * @exception SQLFeatureNotSupportedException
     *                if the JDBC driver does not support this method or this method is not supported for the specified result set type, result set
     *                holdability and result set concurrency.
     */
    public PreparedStatement prepareStatement(String sql,
            int nType,
            int nConcur,
            int nHold,
            SQLServerStatementColumnEncryptionSetting stmtColEncSetting) throws SQLServerException {
        checkClosed();
        return wrappedConnection.prepareStatement(sql, nType, nConcur, nHold, stmtColEncSetting);
    }

    /* L3 */ public CallableStatement prepareCall(String sql,
            int nType,
            int nConcur,
            int nHold) throws SQLServerException {
        checkClosed();
        return wrappedConnection.prepareCall(sql, nType, nConcur, nHold);
    }

    /**
     * Creates a <code>CallableStatement</code> object that will generate <code>ResultSet</code> objects with the given type and concurrency. This
     * method is the same as the <code>prepareCall</code> method above, but it allows the default result set type, result set concurrency type and
     * holdability to be overridden.
     *
     * @param sql
     *            a <code>String</code> object that is the SQL statement to be sent to the database; may contain on or more '?' parameters
     * @param nType
     *            one of the following <code>ResultSet</code> constants: <code>ResultSet.TYPE_FORWARD_ONLY</code>,
     *            <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>
     * @param nConcur
     *            one of the following <code>ResultSet</code> constants: <code>ResultSet.CONCUR_READ_ONLY</code> or
     *            <code>ResultSet.CONCUR_UPDATABLE</code>
     * @param nHold
     *            one of the following <code>ResultSet</code> constants: <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> or
     *            <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>
     * @param stmtColEncSetting
     *            Specifies how data will be sent and received when reading and writing encrypted columns.
     * @return a new <code>CallableStatement</code> object, containing the pre-compiled SQL statement, that will generate <code>ResultSet</code>
     *         objects with the given type, concurrency, and holdability
     * @exception SQLException
     *                if a database access error occurs, this method is called on a closed connection or the given parameters are not
     *                <code>ResultSet</code> constants indicating type, concurrency, and holdability
     * @exception SQLFeatureNotSupportedException
     *                if the JDBC driver does not support this method or this method is not supported for the specified result set type, result set
     *                holdability and result set concurrency.
     */
    public CallableStatement prepareCall(String sql,
            int nType,
            int nConcur,
            int nHold,
            SQLServerStatementColumnEncryptionSetting stmtColEncSetiing) throws SQLServerException {
        checkClosed();
        return wrappedConnection.prepareCall(sql, nType, nConcur, nHold, stmtColEncSetiing);
    }

    /* JDBC 3.0 Auto generated keys */

    /* L3 */ public PreparedStatement prepareStatement(String sql,
            int flag) throws SQLServerException {
        checkClosed();
        return wrappedConnection.prepareStatement(sql, flag);
    }

    /**
     * Creates a default <code>PreparedStatement</code> object that has the capability to retrieve auto-generated keys. The given constant tells the
     * driver whether it should make auto-generated keys available for retrieval. This parameter is ignored if the SQL statement is not an
     * <code>INSERT</code> statement, or an SQL statement able to return auto-generated keys (the list of such statements is vendor-specific).
     * <P>
     * <B>Note:</B> This method is optimized for handling parametric SQL statements that benefit from precompilation. If the driver supports
     * precompilation, the method <code>prepareStatement</code> will send the statement to the database for precompilation. Some drivers may not
     * support precompilation. In this case, the statement may not be sent to the database until the <code>PreparedStatement</code> object is
     * executed. This has no direct effect on users; however, it does affect which methods throw certain SQLExceptions.
     * <P>
     * Result sets created using the returned <code>PreparedStatement</code> object will by default be type <code>TYPE_FORWARD_ONLY</code> and have a
     * concurrency level of <code>CONCUR_READ_ONLY</code>. The holdability of the created result sets can be determined by calling
     * {@link #getHoldability}.
     *
     * @param sql
     *            an SQL statement that may contain one or more '?' IN parameter placeholders
     * @param flag
     *            a flag indicating whether auto-generated keys should be returned; one of <code>Statement.RETURN_GENERATED_KEYS</code> or
     *            <code>Statement.NO_GENERATED_KEYS</code>
     * @param stmtColEncSetting
     *            Specifies how data will be sent and received when reading and writing encrypted columns.
     * @return a new <code>PreparedStatement</code> object, containing the pre-compiled SQL statement, that will have the capability of returning
     *         auto-generated keys
     * @exception SQLException
     *                if a database access error occurs, this method is called on a closed connection or the given parameter is not a
     *                <code>Statement</code> constant indicating whether auto-generated keys should be returned
     * @exception SQLFeatureNotSupportedException
     *                if the JDBC driver does not support this method with a constant of Statement.RETURN_GENERATED_KEYS
     */
    public PreparedStatement prepareStatement(String sql,
            int flag,
            SQLServerStatementColumnEncryptionSetting stmtColEncSetting) throws SQLServerException {
        checkClosed();
        return wrappedConnection.prepareStatement(sql, flag, stmtColEncSetting);
    }

    /* L3 */ public PreparedStatement prepareStatement(String sql,
            int[] columnIndexes) throws SQLServerException {
        checkClosed();
        return wrappedConnection.prepareStatement(sql, columnIndexes);
    }

    /**
     * Creates a default <code>PreparedStatement</code> object capable of returning the auto-generated keys designated by the given array. This array
     * contains the indexes of the columns in the target table that contain the auto-generated keys that should be made available. The driver will
     * ignore the array if the SQL statement is not an <code>INSERT</code> statement, or an SQL statement able to return auto-generated keys (the list
     * of such statements is vendor-specific).
     * <p>
     * An SQL statement with or without IN parameters can be pre-compiled and stored in a <code>PreparedStatement</code> object. This object can then
     * be used to efficiently execute this statement multiple times.
     * <P>
     * <B>Note:</B> This method is optimized for handling parametric SQL statements that benefit from precompilation. If the driver supports
     * precompilation, the method <code>prepareStatement</code> will send the statement to the database for precompilation. Some drivers may not
     * support precompilation. In this case, the statement may not be sent to the database until the <code>PreparedStatement</code> object is
     * executed. This has no direct effect on users; however, it does affect which methods throw certain SQLExceptions.
     * <P>
     * Result sets created using the returned <code>PreparedStatement</code> object will by default be type <code>TYPE_FORWARD_ONLY</code> and have a
     * concurrency level of <code>CONCUR_READ_ONLY</code>. The holdability of the created result sets can be determined by calling
     * {@link #getHoldability}.
     *
     * @param sql
     *            an SQL statement that may contain one or more '?' IN parameter placeholders
     * @param columnIndexes
     *            an array of column indexes indicating the columns that should be returned from the inserted row or rows
     * @param stmtColEncSetting
     *            Specifies how data will be sent and received when reading and writing encrypted columns.
     * @return a new <code>PreparedStatement</code> object, containing the pre-compiled statement, that is capable of returning the auto-generated
     *         keys designated by the given array of column indexes
     * @exception SQLException
     *                if a database access error occurs or this method is called on a closed connection
     * @exception SQLFeatureNotSupportedException
     *                if the JDBC driver does not support this method
     */
    public PreparedStatement prepareStatement(String sql,
            int[] columnIndexes,
            SQLServerStatementColumnEncryptionSetting stmtColEncSetting) throws SQLServerException {
        checkClosed();
        return wrappedConnection.prepareStatement(sql, columnIndexes, stmtColEncSetting);
    }

    /* L3 */ public PreparedStatement prepareStatement(String sql,
            String[] columnNames) throws SQLServerException {
        checkClosed();
        return wrappedConnection.prepareStatement(sql, columnNames);
    }

    /**
     * Creates a default <code>PreparedStatement</code> object capable of returning the auto-generated keys designated by the given array. This array
     * contains the names of the columns in the target table that contain the auto-generated keys that should be returned. The driver will ignore the
     * array if the SQL statement is not an <code>INSERT</code> statement, or an SQL statement able to return auto-generated keys (the list of such
     * statements is vendor-specific).
     * <P>
     * An SQL statement with or without IN parameters can be pre-compiled and stored in a <code>PreparedStatement</code> object. This object can then
     * be used to efficiently execute this statement multiple times.
     * <P>
     * <B>Note:</B> This method is optimized for handling parametric SQL statements that benefit from precompilation. If the driver supports
     * precompilation, the method <code>prepareStatement</code> will send the statement to the database for precompilation. Some drivers may not
     * support precompilation. In this case, the statement may not be sent to the database until the <code>PreparedStatement</code> object is
     * executed. This has no direct effect on users; however, it does affect which methods throw certain SQLExceptions.
     * <P>
     * Result sets created using the returned <code>PreparedStatement</code> object will by default be type <code>TYPE_FORWARD_ONLY</code> and have a
     * concurrency level of <code>CONCUR_READ_ONLY</code>. The holdability of the created result sets can be determined by calling
     * {@link #getHoldability}.
     *
     * @param sql
     *            an SQL statement that may contain one or more '?' IN parameter placeholders
     * @param columnNames
     *            an array of column names indicating the columns that should be returned from the inserted row or rows
     * @param stmtColEncSetting
     *            Specifies how data will be sent and received when reading and writing encrypted columns.
     * @return a new <code>PreparedStatement</code> object, containing the pre-compiled statement, that is capable of returning the auto-generated
     *         keys designated by the given array of column names
     * @exception SQLException
     *                if a database access error occurs or this method is called on a closed connection
     * @exception SQLFeatureNotSupportedException
     *                if the JDBC driver does not support this method
     */
    public PreparedStatement prepareStatement(String sql,
            String[] columnNames,
            SQLServerStatementColumnEncryptionSetting stmtColEncSetting) throws SQLServerException {
        checkClosed();
        return wrappedConnection.prepareStatement(sql, columnNames, stmtColEncSetting);
    }

    /* JDBC 3.0 Savepoints */

    /* L3 */ public void releaseSavepoint(Savepoint savepoint) throws SQLServerException {
        checkClosed();
        wrappedConnection.releaseSavepoint(savepoint);
    }

    /* L3 */ public Savepoint setSavepoint(String sName) throws SQLServerException {
        checkClosed();
        return wrappedConnection.setSavepoint(sName);
    }

    /* L3 */ public Savepoint setSavepoint() throws SQLServerException {
        checkClosed();
        return wrappedConnection.setSavepoint();
    }

    /* L3 */ public void rollback(Savepoint s) throws SQLServerException {
        checkClosed();
        wrappedConnection.rollback(s);
    }

    /* L3 */ public int getHoldability() throws SQLServerException {
        checkClosed();
        return wrappedConnection.getHoldability();
    }

    /* L3 */ public void setHoldability(int nNewHold) throws SQLServerException {
        checkClosed();
        wrappedConnection.setHoldability(nNewHold);
    }

    public int getNetworkTimeout() throws SQLException {
        checkClosed();
        return wrappedConnection.getNetworkTimeout();
    }

    public void setNetworkTimeout(Executor executor,
            int timeout) throws SQLException {
        checkClosed();
        wrappedConnection.setNetworkTimeout(executor, timeout);
    }

    public String getSchema() throws SQLException {
        checkClosed();
        return wrappedConnection.getSchema();
    }

    public void setSchema(String schema) throws SQLException {
        checkClosed();
        wrappedConnection.setSchema(schema);
    }

    public java.sql.Array createArrayOf(String typeName,
            Object[] elements) throws SQLException {
        checkClosed();
        return wrappedConnection.createArrayOf(typeName, elements);
    }

    public Blob createBlob() throws SQLException {
        checkClosed();
        return wrappedConnection.createBlob();
    }

    public Clob createClob() throws SQLException {
        checkClosed();
        return wrappedConnection.createClob();
    }

    public NClob createNClob() throws SQLException {
        checkClosed();
        return wrappedConnection.createNClob();
    }

    public SQLXML createSQLXML() throws SQLException {
        checkClosed();
        return wrappedConnection.createSQLXML();
    }

    public Struct createStruct(String typeName,
            Object[] attributes) throws SQLException {
        checkClosed();
        return wrappedConnection.createStruct(typeName, attributes);
    }

    public Properties getClientInfo() throws SQLException {
        checkClosed();
        return wrappedConnection.getClientInfo();
    }

    public String getClientInfo(String name) throws SQLException {
        checkClosed();
        return wrappedConnection.getClientInfo(name);
    }

    public void setClientInfo(Properties properties) throws SQLClientInfoException {
        // No checkClosed() call since we can only throw SQLClientInfoException from here
        wrappedConnection.setClientInfo(properties);
    }

    public void setClientInfo(String name,
            String value) throws SQLClientInfoException {
        // No checkClosed() call since we can only throw SQLClientInfoException from here
        wrappedConnection.setClientInfo(name, value);
    }

    public boolean isValid(int timeout) throws SQLException {
        checkClosed();
        return wrappedConnection.isValid(timeout);
    }

    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        wrappedConnection.getConnectionLogger().entering(toString(), "isWrapperFor", iface);
        boolean f = iface.isInstance(this);
        wrappedConnection.getConnectionLogger().exiting(toString(), "isWrapperFor", f);
        return f;
    }

    public <T> T unwrap(Class<T> iface) throws SQLException {
        wrappedConnection.getConnectionLogger().entering(toString(), "unwrap", iface);
        T t;
        try {
            t = iface.cast(this);
        }
        catch (ClassCastException e) {
            SQLServerException newe = new SQLServerException(e.getMessage(), e);
            throw newe;
        }
        wrappedConnection.getConnectionLogger().exiting(toString(), "unwrap", t);
        return t;
    }

    public UUID getClientConnectionId() throws SQLServerException {
        checkClosed();
        return wrappedConnection.getClientConnectionId();
    }

    /**
     * Modifies the setting of the sendTimeAsDatetime connection property. When true, java.sql.Time values will be sent to the server as SQL
     * Serverdatetime values. When false, java.sql.Time values will be sent to the server as SQL Servertime values. sendTimeAsDatetime can also be
     * modified programmatically with SQLServerDataSource.setSendTimeAsDatetime. The default value for this property may change in a future release.
     * 
     * @param sendTimeAsDateTimeValue
     *            enables/disables setting the sendTimeAsDatetime connection property. For more information about how the Microsoft JDBC Driver for
     *            SQL Server configures java.sql.Time values before sending them to the server, see
     *            <a href="https://msdn.microsoft.com/en-us/library/ff427224(v=sql.110).aspx" > Configuring How java.sql.Time Values are Sent to the
     *            Server.
     */
    public synchronized void setSendTimeAsDatetime(boolean sendTimeAsDateTimeValue) throws SQLServerException {
        checkClosed();
        wrappedConnection.setSendTimeAsDatetime(sendTimeAsDateTimeValue);
    }

    /**
     * Returns the setting of the sendTimeAsDatetime connection property.
     * 
     * @return if enabled, returns true. Otherwise, false.
     * @throws SQLServerException
     *             when an error occurs.
     */
    public synchronized final boolean getSendTimeAsDatetime() throws SQLServerException {
        checkClosed();
        return wrappedConnection.getSendTimeAsDatetime();
    }
}
